/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.heron-language.com
    Translates an AST generated from Heron code into CVML
*/

#ifndef HERON_TO_CVML_HPP
#define HERON_TO_CVML_HPP

#include <string>
#include <vector>
#include <map>
#include <exception>
#include <iostream>

#include "cvml.hpp"

/*
namespace heron2cvml
{
    using namespace heron_ast;

    class HeronToCVML
    {
        // typedefs
        typedef AstNodeList<SimpleExpr> ExprList;
        
        // fields
        ByteStream* stream;

        // private    functions
        void WriteDefaultTypeValue(TypeExpr* type) {
            if (type->ToString() == "int") 
                WriteInstruction(op_push_int, 0, "default int");
            else if (type->ToString() == "char") 
                WriteInstruction(op_push_char, 0, "default char");
            else if (StringBeginsWith(type->ToString(), "Array")) 
                WriteInstruction(op_nil, -1, "default array");
            else if (type->ToString() == "List") 
                WriteInstruction(op_nil, -1, "default list");
            else if (type->ToString() == "Object") 
                WriteInstruction(op_null, -1, "default object");
            else 
                Error("Could not find default value for type " + type->ToString());
        }

        void BeginScope() {
            stream->BeginScope();
        }

        void EndScope() {
            stream->EndScope();
        }

        void BeginFunction(std::string name) {
            stream->BeginFunction(name);
        }

        void EndFunction() {
            stream->EndFunction();
        }

        Subroutine* BeginQuotation() {
            return stream->BeginQuotation();
        }

        void EndQuotation() {
            stream->EndQuotation();
        }

        void WriteInstruction(OpCodeEnum opcode, int operand = -1, std::string comment = "") {
            stream->AddInstruction(opcode, operand, comment);
            if (tracing) {
                printf("%s %d // %s\n", OpCodeToPChar(opcode), operand, comment.c_str());
            }
        }

        void WriteComment(std::string s) {
            if (tracing) {
                printf("// %s\n", s.c_str());
            }
        }

        void WriteQuotation(OpCodeEnum opcode, int operand = -1) {
            BeginQuotation();
            WriteInstruction(opcode, operand);
            EndQuotation();
        }

    public:

        ///////////////////////////////////////////////////////////////////////////////////////////
        // Ctors

        HeronToCVML(ByteStream* _stream)             
            : stream(_stream)
        {
        }

        ///////////////////////////////////////////////////////////////////////////////////////////
        // These are the writing functions.

        void Write(std::string s) {
            std::cout << s << std::endl;
        }    

        bool ExprMatches(ExprList& exprlist, int& cur, char* x) {
            if (cur >= exprlist.Count()) 
                return false;
            std::string s(exprlist.At(cur)->expr->ToString());
            if (s == x) {
                ++cur;
                return true;
            }
            return false;
        }

        template<typename T>
        bool ExprIsTypeNoAdvance(ExprList& exprlist, int cur) {
            if (cur >= exprlist.Count())
                return false;
            return exprlist.At(cur)->expr->template TypeMatches<T>();
        }

        // Note: I am not happy that this advances "cur".
        bool IsAssignment(ExprList& exprlist, int& cur) {
            if (cur >= exprlist.Count())
                return false;
            return ExprMatches(exprlist, cur, "=");
        }
		
		bool IsSimpleAssignment(Expr* expr) {
			if (expr->exprs.Count() < 3) 
				return false;
			if (expr->exprs.At(1)->ToString() != "=")
				return false;
			return true;
		}

        void WriteLiteral(ExprList& exprlist, int& cur) {
            if (cur >= exprlist.Count())
                return;
            Literal* lit = exprlist.At(cur++)->expr->CastTo<Literal>();
            Assert(lit != NULL, "expected a literal");

            if (lit->value->TypeMatches<BinNumber>()) {
                WriteInstruction(op_push_int, StrToInt(lit->value->ToString()), lit->value->ToString());
            }
            else if (lit->value->TypeMatches<HexNumber>()) {
                WriteInstruction(op_push_int, StrToInt(lit->value->ToString()), lit->value->ToString());
            }
            else if (lit->value->TypeMatches<DecNumber>()) {
                WriteInstruction(op_push_int, StrToInt(lit->value->ToString()), lit->value->ToString());
            }
            else if (lit->value->TypeMatches<CharLiteral>()) {
                // TODO: convert from string rep to char
                WriteInstruction(op_push_char, 0, lit->value->ToString());
            }
            else if (lit->value->TypeMatches<StringLiteral>()) {
                WriteString(lit->value->ToString());
            }
        }

        void WriteString(std::string s) {
            stream->AddString(s);
        }

        void WriteNewExpr(ExprList& exprlist, int& cur) {
            if (cur >= exprlist.Count())
                return;
            AstNode* expr = exprlist.At(cur++)->expr;
            NewExpr* newexpr = dynamic_cast<NewExpr*>(expr);
            WriteParanthesizedExpr(newexpr->args);
            WriteString(newexpr->type->ToString());
            WriteInstruction(op_new);
        }

        void WriteName(ExprList& exprlist, int& cur) {
            if (cur >= exprlist.Count()) 
                return;
            SimpleExpr* name = exprlist.At(cur++);
            std::string sname = name->expr->ToString();
            
            if (IsAssignment(exprlist, cur))
            {
                int save = cur;
                WriteExpr(exprlist, cur);
                Assert(cur > save, "Missing r-value for assignment");        
                WriteVarAssignment(sname);
            }
            else
            {
                WriteVarRead(sname);
            }                                
        }

        void WriteVarAssignment(std::string s) 
        {
            stream->AddVarAssignment(s);
        }

        void WriteVarRead(std::string s) 
        {
            stream->AddVarRead(s);
        }

        void WriteParanthesizedExpr(ExprList& exprlist, int& cur) {
            if (cur >= exprlist.Count()) 
                return;
            AstNode* expr = exprlist.At(cur)->expr;
            ParanthesizedExpr* paranexpr = dynamic_cast<ParanthesizedExpr*>(expr);
            WriteParanthesizedExpr(paranexpr);
            cur++;
        }

        void WriteParanthesizedExpr(ParanthesizedExpr* expr) {
            WriteComment(expr->ToString());
            if (expr->expr != NULL)
                WriteExpr(expr->expr);
        }

        void WritePrimaryExpr(ExprList& exprlist, int& cur) {
            if (cur >= exprlist.Count()) 
                return;
            if (ExprIsTypeNoAdvance<Name>(exprlist, cur)) {
                WriteName(exprlist, cur);
            }
            else if (ExprIsTypeNoAdvance<Literal>(exprlist, cur)) {
                WriteLiteral(exprlist, cur);
            }
            else if (ExprIsTypeNoAdvance<ParanthesizedExpr>(exprlist, cur)) {
                WriteParanthesizedExpr(exprlist, cur);
            }
            else if (ExprIsTypeNoAdvance<NewExpr>(exprlist, cur)) {
                WriteNewExpr(exprlist, cur);
            }
        }

        std::string GetExprString(ExprList& exprlist, int first, int last) {
            std::string ret = "";
            while (first < last) {
                ret += exprlist.At(first++)->ToString();
            }
            return ret;
        }

        // Inserts arguments before the function value.
        // This is complicated by the fact that a function value can be 
        // a compound expression instead of a simnple name
        void WriteArgs(Instruction* after, Expr* x) {            
            Instruction* save = stream->GetCurrentInstruction();
            stream->SetCurrentInstruction(after);
            WriteComment("arguments");
            WriteExpr(x);
            stream->AddInstructions(save);
        }

        void WritePostfixExpr(ExprList& exprlist, int& cur) {
            int save = cur;
            
            Instruction* savedPos = stream->GetCurrentInstruction();
            WritePrimaryExpr(exprlist, cur);                        

            while (true) { 
                std::string base = GetExprString(exprlist, save, cur);

                if (cur >= exprlist.Count()) 
                    return;
                AstNode* expr = exprlist.At(cur)->expr;

                if (expr->TypeMatches<ParanthesizedExpr>()) {
                    cur++;
                    ParanthesizedExpr* tmp = dynamic_cast<ParanthesizedExpr*>(expr);
                    WriteComment("begin-function-call");
                    if (tmp->expr != NULL)
                        WriteArgs(savedPos, tmp->expr);
                    WriteInstruction(op_apply, -1, base);
                    WriteComment("end-function-call");
                }
                else if (expr->TypeMatches<BracketedExpr>()) {
                    cur++;
                    BracketedExpr* tmp = dynamic_cast<BracketedExpr*>(expr);
                    WriteComment(tmp->ToString());
                    WriteExpr(tmp->expr);
                    if (IsAssignment(exprlist, cur))
                    {
                        int save = cur;
                        WriteExpr(exprlist, cur);
                        Assert(cur > save, "Missing r-value for assignment");
                        
                        // TODO: verify that I don't make a one-off error 
                        // with regards to offsets for "set" functions.
                        WriteInstruction(op_set_at, -1, base + "[" + tmp->expr->ToString() + "] =");
                    }
                    else 
                    {
                        WriteInstruction(op_get_at, -1, base + "[" + tmp->expr->ToString() + "]");
                    }
                }
                else if (ExprMatches(exprlist, cur, ".")) {
                    if (cur >= exprlist.Count() || !exprlist.At(cur)->expr->TypeMatches<Name>()) {
                        Error("Expected a name expression");
                    }
                    AstNode* expr = exprlist.At(cur);

                    std::string s = expr->ToString();
                    WriteComment("." + s);

					++cur;
                    
					if (IsAssignment(exprlist, cur))
                    {
						WriteString(s);

						int save = cur;
                        WriteExpr(exprlist, cur);
                        Assert(cur > save, "Missing r-value for assignment");                
                        WriteInstruction(op_set_slot, -1, base);
                    }
                    else 
                    {
						WriteString(s);
	                    WriteInstruction(op_get_slot, -1, base);
                    }
                }
                else {
                    // TODO: handle postfix ++ and --
                    return;
                }
            }                
        }        

        void WriteUnaryExpr(ExprList& exprlist, int& cur) {
            if (ExprMatches(exprlist, cur, "-")) {
                WritePostfixExpr(exprlist, cur);
                WriteInstruction(op_neg);
            }
            else {
                WritePostfixExpr(exprlist, cur);
            }
        }

        void WriteMulExpr(ExprList& exprlist, int& cur) {
            WriteUnaryExpr(exprlist, cur);
            while (true) {
                if (ExprMatches(exprlist, cur, "*")) {
                    WriteUnaryExpr(exprlist, cur);    
                    WriteInstruction(op_mul);
                }
                else if (ExprMatches(exprlist, cur, "/")) {
                    WriteUnaryExpr(exprlist, cur);    
                    WriteInstruction(op_div);
                }
                else if (ExprMatches(exprlist, cur, "%")) {
                    WriteUnaryExpr(exprlist, cur);    
                    WriteInstruction(op_mod);
                }
                else {
                    return;
                }
            }
        }

        void WriteAddExpr(ExprList& exprlist, int& cur) {
            WriteMulExpr(exprlist, cur);
            while (true) {
                if (ExprMatches(exprlist, cur, "+")) {
                    WriteMulExpr(exprlist, cur);    
                    WriteInstruction(op_add);
                }
                else if (ExprMatches(exprlist, cur, "-")) {
                    WriteMulExpr(exprlist, cur);    
                    WriteInstruction(op_sub);
                }
                else {
                    return;
                }
            }
        }

        void WriteRelExpr(ExprList& exprlist, int& cur) {
            WriteAddExpr(exprlist, cur);
            while (true) {
                if (ExprMatches(exprlist, cur, ">")) {
                    WriteAddExpr(exprlist, cur);    
                    WriteInstruction(op_gt);
                }
                else if (ExprMatches(exprlist, cur, ">=")) {
                    WriteAddExpr(exprlist, cur);    
                    WriteInstruction(op_gteq);
                }
                else if (ExprMatches(exprlist, cur, "<")) {
                    WriteAddExpr(exprlist, cur);    
                    WriteInstruction(op_lt);
                }
                else if (ExprMatches(exprlist, cur, "<=")) {
                    WriteAddExpr(exprlist, cur);    
                    WriteInstruction(op_lteq);
                }
                else {
                    return;
                }
            }
        }

        void WriteEqExpr(ExprList& exprlist, int& cur) {
            WriteRelExpr(exprlist, cur);
            while (true) {
                if (ExprMatches(exprlist, cur, "==")) {
                    WriteRelExpr(exprlist, cur);    
                    WriteInstruction(op_eq);
                }
                else if (ExprMatches(exprlist, cur, "!=")) {
                    WriteRelExpr(exprlist, cur);    
                    WriteInstruction(op_neq);
                }
                else {
                    return;
                }
            }
        }

        void WriteLogXOrExpr(ExprList& exprlist, int& cur) {
            WriteEqExpr(exprlist, cur);
            while (ExprMatches(exprlist, cur, "^^")) {
                WriteEqExpr(exprlist, cur);                        
                WriteInstruction(op_xor);
            }
        }

        void WriteLogAndExpr(ExprList& exprlist, int& cur) {
            WriteLogXOrExpr(exprlist, cur);
            while (ExprMatches(exprlist, cur, "&&")) {
                BeginQuotation();
                WriteLogXOrExpr(exprlist, cur);                        
                EndQuotation();
                WriteQuotation(op_push_false);
                WriteInstruction(op_if);
            }
        }

        void WriteLogOrExpr(ExprList& exprlist, int& cur) {
            WriteLogAndExpr(exprlist, cur);
            while (ExprMatches(exprlist, cur, "||")) {
                WriteQuotation(op_push_true);
                BeginQuotation();
                WriteLogAndExpr(exprlist, cur);                        
                EndQuotation();
                WriteInstruction(op_if);
            }
        }

        void WriteCondExpr(ExprList& exprlist, int& cur) {            
            WriteLogOrExpr(exprlist, cur);
            while (ExprMatches(exprlist, cur, "?")) {
                BeginQuotation();
                WriteLogOrExpr(exprlist, cur);    
                EndQuotation();
                if (ExprMatches(exprlist, cur, ":")) {
                    BeginQuotation();
                    WriteLogOrExpr(exprlist, cur);
                    EndQuotation();
                }
                else {
                    Error("Expected alternative value");
                }
                WriteInstruction(op_if);
            }
        }

        void WriteExpr(ExprList& x, int& cur) {
            WriteCondExpr(x, cur);
        }

        void WriteExpr(Expr* x) {
            WriteComment("begin-expression");
            WriteComment(x->ToString());
            int cur = 0;
            while (cur < x->exprs.Count()) {
                WriteExpr(x->exprs, cur);
            }
            WriteComment("end-expression");
        }

        void WriteExprStatement(ExprStatement* x) {
            WriteComment("begin-expression-statement");
            WriteExpr(x->expr);
            WriteComment("end-expression-statement");
        }

        void WriteReturnStatement(ReturnStatement* x) {
            WriteComment("begin-return-statement");
            WriteExpr(x->expr);
            WriteInstruction(op_noop, -1, "return");
            WriteComment("end-return-statement");
        }

        void WriteVarDecl(VarDecl* x) {
            std::string name = x->name->ToString();
            WriteComment("begin-variable-declaration " + name);
            stream->MarkAsInitializer();
            if (x->init != NULL) {
                WriteExpr(x->init); 
            }
            else {
                WriteDefaultTypeValue(x->type);
            }
            stream->AddDefn(name);
            WriteComment("end-variable-declaration");
        }

		void RemoveVar(std::string s) {
			stream->RemoveDefn(s);
		}

        void WriteWhileStatement(WhileStatement* x) {
            WriteComment("begin-while-statement");
            BeginQuotation();
            WriteComment("begin-while-body");
            WriteStatement(x->body);
            WriteComment("end-while-body");
            EndQuotation();

            BeginQuotation();
            WriteComment("begin-while-conditon");
            WriteExpr(x->cond);        

            WriteComment("end-while-conditon");
            EndQuotation();

            WriteInstruction(op_while);
            WriteComment("end-while-statement");
        }

        void WriteForStatement(ForStatement* x) 
        {
            WriteComment("begin-for-statement");
            if (x->vardecl->init == NULL)
                Error("Variable declaration statement expected with initializer");
			WriteVarDecl(x->vardecl);

			std::string sVar = x->vardecl->name->ToString();

			// Write out the iterative step
            BeginQuotation();
			if (!IsSimpleAssignment(x->iter)) {
				Error("Only simple assignment to the index variable is supported in a for loop");
			}
			// only write out the r-value
			int cur = 2;
			WriteExpr(x->iter->exprs, cur);
			// return it
            WriteInstruction(op_noop, -1, "return");
			EndQuotation();
            
			// Write out the invariant
            BeginQuotation();
            WriteExpr(x->inv);
            EndQuotation();

			// Write out the body
            BeginQuotation();            
            WriteStatement(x->body);
            EndQuotation();

            WriteInstruction(op_for);
			// the index variable will be popped automatically.
			RemoveVar(sVar); 
            WriteComment("end-for-statement");
        }

        void WriteForEachIndex(ForEachStatement* x) 
        {
			// TODO: fix this. Base it on the "for" loop.
            WriteComment("begin-foreach-index-statement");

            // HACK: assumes the form .Indexes()
            AstNodeList<SimpleExpr> tmp;
            for (int i = 0; i < x->coll->exprs.Count() - 3; ++i) {
                tmp.Add(x->coll->exprs.At(i));
            }
            int cur = 0;
            while (cur < tmp.Count()) {
                WriteExpr(tmp, cur);
            }
            
            BeginQuotation();
            BeginScope();
            stream->AddArg(x->name->ToString());
            WriteStatement(x->body);
            EndScope();
            EndQuotation();

            WriteInstruction(op_foreach_index);
            WriteComment("end-foreach-index-statement");
        }

        void WriteForEachStatement(ForEachStatement* x) 
        {
            if (x->IsForEachIndex()) {
                WriteForEachIndex(x);
                return;
            }

            WriteComment("begin-foreach-statement");
            BeginScope();

            BeginQuotation();            
            stream->AddArg(x->name->ToString());
            WriteExpr(x->coll);
            EndQuotation();
            
            BeginQuotation();
            WriteStatement(x->body);
            EndQuotation();

            WriteInstruction(op_foreach);
            EndScope();
            WriteComment("end-foreach-statement");
        }

        void WriteStatementAsQuotation(Statement* x) {
            BeginQuotation();
            if (x != NULL) 
                WriteStatement(x);
            EndQuotation();
        }

        void WriteIfStatement(IfStatement* x) {
            WriteComment("begin-if-statement");

            WriteComment("begin-if-condition");
            WriteExpr(x->cond);
            WriteComment("end-if-condition");

            WriteComment("begin-if-body");
            WriteStatementAsQuotation(x->body);
            WriteComment("end-if-body");

            if (x->alternative != NULL) {
                WriteComment("begin-else-statement");
                WriteStatementAsQuotation(x->alternative);            
                WriteComment("end-else-statement");
                WriteInstruction(op_if);
            }
            else {
				WriteInstruction(op_push_noop);
                WriteInstruction(op_if);
            }
            WriteComment("end-if-statement");
        }

        void WriteCodeBlock(CodeBlock* x) {
            WriteComment("begin-code-block");
            BeginScope();
            for (int i=0; i < x->statements.Count(); ++i)
                WriteStatement(x->statements.At(i));
            EndScope();
            WriteComment("end-code-block");
        }

        void WriteStatement(Statement* x) {
			stream->AddStatement();
			AstNode* child = x->statement;
            if (child->TypeMatches<ForStatement>())
                WriteForStatement(dynamic_cast<ForStatement*>(child));
            else if (child->TypeMatches<ForEachStatement>())
                WriteForEachStatement(dynamic_cast<ForEachStatement*>(child));
            else if (x->statement->TypeMatches<CodeBlock>()) 
                WriteCodeBlock(dynamic_cast<CodeBlock*>(child));
            else if (x->statement->TypeMatches<VarDecl>()) 
                WriteVarDecl(dynamic_cast<VarDecl*>(child));
            else if (x->statement->TypeMatches<IfStatement>()) 
                WriteIfStatement(dynamic_cast<IfStatement*>(child));
            else if (x->statement->TypeMatches<WhileStatement>()) 
                WriteWhileStatement(dynamic_cast<WhileStatement*>(child));
            else if (x->statement->TypeMatches<ReturnStatement>()) 
                WriteReturnStatement(dynamic_cast<ReturnStatement*>(child));
            else if (x->statement->TypeMatches<ExprStatement>()) 
                WriteExprStatement(dynamic_cast<ExprStatement*>(child));
            else 
                Error("Unrecognized statement type: " + x->ToString());
        }

        void WriteFunction(Function* x) {
            Assert(x != NULL, "Missing function");
            BeginFunction(x->name->ToString());
            BeginScope();
            for (int i=0; i < x->args->args.Count(); ++i) {
                Arg* arg = x->args->args.At(i);
                stream->AddArg(arg->name->ToString());
            }
            WriteCodeBlock(x->body);            
            EndScope();
            EndFunction();
        }

        void WriteScript(ScriptFile* x) {
            for (int i=0; i < x->GetNumChildren(); ++i) {
                WriteFunction(x->GetChild(i)->CastTo<Function>());
            }
        }
    };

    void Convert(ByteCode& bytecode, ScriptFile& script) {
        std::auto_ptr<ByteStream> stream(new ByteStream(&bytecode));
        HeronToCVML converter(stream.get());
        converter.WriteScript(&script);
    }
}
*/


#endif 
